﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImportStep.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ExecutionTool.ImportExport
{
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;

    using ExecutionTool.Common;
    using ExecutionTool.CopyDatabase;
    using ExecutionTool.Profile;

    using Microsoft.Practices.EnterpriseLibrary.Logging;

    public class ImportStep : StepBase<ImportStepInfo>
    {
        #region Fields

        private readonly string blobUri;

        private readonly string databaseName;

        private readonly ExportStepInfo exportStepInfo;

        private readonly IImportExportServiceAdatper importExportServiceAdatper;

        private readonly bool isRestore;

        private readonly ISqlAzureAdapter sqlAzureAdapter;

        private readonly SqlAzureLocation sqlAzureLocation;

        private readonly StepType stepType;

        private readonly IStorageAdapter storageAdapter;

        private DataCenter dataCenter;

        private ImportStepInfo importStepInfo;

        private bool isFinishing;

        #endregion

        #region Constructors and Destructors

        public ImportStep(
            Profile profile, 
            IStorageAdapter storageAdapter, 
            IImportExportServiceAdatper importExportServiceAdatper, 
            ISqlAzureAdapter sqlAzureAdapter, 
            LogWriter logger, 
            string blobName, 
            ExportStepInfo exportStepInfo, 
            string databaseName = null)
            : base(profile, true, logger)
        {
            Helper.CheckForNull(storageAdapter, "storageAdapter");
            Helper.CheckForNull(importExportServiceAdatper, "importExportServiceAdatper");
            Helper.CheckForNull(sqlAzureAdapter, "sqlAzureAdapter");
            Helper.CheckForNull(blobName, "blobName");

            this.storageAdapter = storageAdapter;
            this.importExportServiceAdatper = importExportServiceAdatper;
            this.sqlAzureAdapter = sqlAzureAdapter;

            this.blobUri = BuildBlobUri(this.Profile.TargetAzureStorage, BuildBlobAddress(this.Profile.Name.ToLower(), blobName));

            this.exportStepInfo = exportStepInfo;
            this.isRestore = exportStepInfo == null;
            this.stepType = this.isRestore ? StepType.ImportRestore : StepType.ImportVerification;
            this.sqlAzureLocation = this.isRestore ? this.Profile.Source : this.Profile.TargetSqlAzureVerification;

            this.databaseName = string.IsNullOrWhiteSpace(databaseName) ? this.Profile.Source.DatabaseName : databaseName;
        }

        #endregion

        #region Methods

        protected override void AbortAction()
        {
            // drop the database being imported and stop the import operation (this might take a while as seeing in the experiment)
            if (!this.isFinishing && this.importStepInfo != null)
            {
                this.sqlAzureAdapter.DeleteDatabase(this.sqlAzureLocation, this.importStepInfo.TempDatabaseName);
            }
        }

        protected override void CleanupAction()
        {
            // for this step, Cleanup happens to be the same action as Abort
            this.AbortAction();
        }

        protected override void OnFinished()
        {
            /*
             * rename just imported db to the specified name if this is a restore operation
             * 
             * drop database if this is a verification operation
             * 
             */
            if (this.isRestore)
            {
                this.isFinishing = true;

                this.sqlAzureAdapter.RenameDatabase(this.Profile.Source, this.importStepInfo.TempDatabaseName, this.databaseName);
            }
            else
            {
                this.sqlAzureAdapter.DeleteDatabase(this.Profile.TargetSqlAzureVerification, this.importStepInfo.TempDatabaseName);
            }

            base.OnFinished();
        }

        protected override double ProgressHeartbeatAction()
        {
            var newProgress = this.importExportServiceAdatper.GetOperationProgress(
                this.dataCenter.Endpoint, this.sqlAzureLocation, this.importStepInfo.StatusTrackId, this.stepType);
            return newProgress;
        }

        protected override ImportStepInfo StartAction()
        {
            /*
             * - trigger import operation
             *  - make sure blob exist (only for restore)
             *  - make sure target database not exist (only for restore)
             *  - detect data center endpoint
             *  - make web request to trigger the operation
             */
            SqlAzureDatabaseInfo info = null;

            var tasks = new List<Task>();

            if (this.isRestore)
            {
                var blobTask = Task.Factory.StartNew(
                    () =>
                        {
                            var exists = this.storageAdapter.BlobExists(this.Profile.TargetAzureStorage, this.blobUri);

                            if (!exists)
                            {
                                throw new StepFaultException(
                                    string.Format("The specified blob [{0}] doesn't exist.", this.blobUri), 
                                    StepType.ImportRestore, 
                                    ErrorCodes.ImportExportImportPrerequisiteError);
                            }
                        });
                tasks.Add(blobTask);

                var sqlTask = Task.Factory.StartNew(
                    () =>
                        {
                            var exists = this.sqlAzureAdapter.DatabaseExists(this.Profile.Source, this.databaseName);

                            if (exists)
                            {
                                throw new StepFaultException(
                                    string.Format(
                                        "The specified database [{0}] on [{1}] already exists.", this.databaseName, this.Profile.Source.ServerName), 
                                    StepType.ImportRestore, 
                                    ErrorCodes.ImportExportImportPrerequisiteError);
                            }
                        });
                tasks.Add(sqlTask);

                var blobMetadataTask =
                    Task.Factory.StartNew(() => this.storageAdapter.GetMetadata(this.Profile.TargetAzureStorage, this.blobUri)).ContinueWith(
                        t =>
                            {
                                var metadata = t.Result;
                                string edition;
                                string maxSizeInGb;
                                if (!metadata.TryGetValue("Edition", out edition) || !metadata.TryGetValue("MaxSizeInGb", out maxSizeInGb))
                                {
                                    throw new StepFaultException(
                                        string.Format("Failed to find necessary metadata for the specified blob [{0}].", this.blobUri), 
                                        this.stepType, 
                                        ErrorCodes.ImportExportImportPrerequisiteError);
                                }

                                info = new SqlAzureDatabaseInfo(edition, maxSizeInGb + "GB");
                            });
                tasks.Add(blobMetadataTask);
            }
            else
            {
                info = this.exportStepInfo.DatabaseInfo;
            }

            var dataCenterTask = DataCenterMapper.GetDatacenterFromServerNameAsync(this.sqlAzureLocation.ServerName);
            tasks.Add(dataCenterTask);

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                throw new StepFaultException(
                    "Pre-requisite work failed during export step.", this.stepType, ErrorCodes.ImportExportImportPrerequisiteError, ex);
            }

            this.dataCenter = dataCenterTask.Result;
            var tempDbName = Guid.NewGuid().ToString("D");

            var requestsId = this.importExportServiceAdatper.TriggerImport(
                this.dataCenter.Endpoint, this.sqlAzureLocation, this.Profile.TargetAzureStorage, this.blobUri, info, tempDbName);
            this.importStepInfo = new ImportStepInfo(requestsId, tempDbName);
            return this.importStepInfo;
        }

        private static string BuildBlobAddress(string containerName, string blobName)
        {
            var name = string.Format("{0}/{1}", containerName, blobName);
            return name;
        }

        private static string BuildBlobUri(AzureStorageLocation targetAzureStorage, string blobAddress)
        {
            return string.Format("https://{0}.blob.core.windows.net/{1}", targetAzureStorage.StorageAccount, blobAddress);
        }

        #endregion
    }
}
